package it.avantsoft.ls.gl;

import it.avantsoft.ls.base.Application;
import it.avantsoft.ls.base.Configuration;
import it.avantsoft.ls.base.Protocol;
import it.avantsoft.ls.gl.data.AddressBookManager;
import it.avantsoft.ls.gl.data.Appraisals;
import it.avantsoft.ls.peer.PeerHandler;
import it.avantsoft.ls.util.SimpleEntry;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class GroupLeaderHandler extends Thread {
    private static Logger log = Logger.getLogger(GroupLeaderHandler.class);

    //TODO isServer
    //falso: - non gestisce i comandi di WelcomeCommand
    //       - utilizza una porta non nota per ricevere connessioni tcp
    //       - accetta Child indipendentemente dalla ListAllowedPeer

    private boolean isServer;
    private int number;
    private AddressBookManager addressBookManager;
    private ServerSocket serverSocket;
    private AcceptorChild acceptorChild;
    private SimpleEntry<String, Integer> selfInfo;
    private Map<String, GroupChildHandler> connectingChild;

    private boolean stop;

    private Application application;

    public GroupLeaderHandler(Application application, boolean isServer, int number, AddressBookManager addressBookManager) {
//        super((number == 0) ? "Server" : "GroupLeaderHandler " + number);
        super("GroupLeaderHandler " + number);
        this.application = application;
        this.isServer = isServer;
        this.number = number;
        this.addressBookManager = addressBookManager;

        try {
            //non sara' qualunque porta ma quella configurata
            int port = isServer ? Configuration.SERVER_TCP_PORT : 0;

            serverSocket = new ServerSocket(port);
            //log.info("listening on port: " + getListeningPort());
        } catch (IOException e) {
            e.printStackTrace();
        }

        connectingChild = new HashMap<String, GroupChildHandler>();

        acceptorChild = new AcceptorChild(serverSocket, this);
        acceptorChild.start();

        stop = false;
    }

    public void shutdown() {
        acceptorChild.shutdown();
        for (GroupChildHandler groupChildHandler : addressBookManager.getLowerAB().getAll()) {
            groupChildHandler.shutdown();
        }
    }

    public int getNumber() {
        return number;
    }

    public int getListeningPort() {
        return serverSocket.getLocalPort();
    }

    public String getHostAddress() {
        String hostAddress = null;
        try {
            hostAddress = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("get host address", e);
        }
        log.info("Server Socket Host Address : " + hostAddress);
        return hostAddress;
    }

    private PeerHandler getPeerHandler() {
        return application.getPeerHandler();
    }

    public String getPeerId() {
        PeerHandler peerHandler = application.getPeerHandler();
        if (peerHandler != null) {
            return peerHandler.getPeerId();
        } else {
            return null;
        }
    }

    public String toString() {
        return "GroupLeaderHandler{" +
                "isServer=" + isServer +
                ", serverSocket=" + serverSocket +
                '}';
    }

    public void run() {
        Object sync = addressBookManager.getSync();
        while (!stop) {
            Protocol.ABProtocol state;
            synchronized (sync) {
                try {
                    log.info("Waiting AddressBook State Change");
                    sync.wait();
                    state = addressBookManager.getState();
                    log.info("AddressBook state detected: " + state);
                    if (state != Protocol.ABProtocol.INITIALIZED) {//Event Detected
                        resolveState(state);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    sync.notify();
                }
            }
        }
    }

    private void resolveState(Protocol.ABProtocol state) {

        switch (state) {
            case HBALANCEDETECTED://do something
                GroupChildHandler mostLoaded = addressBookManager.getMostLoadedPeer();
                GroupChildHandler lessLoaded = addressBookManager.getLessLoadedPeer();

                mostLoaded.sendHorizontalBalance(addressBookManager.getLoadDifference(), lessLoaded);

                //TODO ExchangeablePeerCommand su lessLoaded
                break;

            case VBALANCEDETECTED://do something
                break;
        }
        //problema analizzato e, se possibile, risolto, porto tutto allo stato initialized!
        addressBookManager.setState(Protocol.ABProtocol.INITIALIZED);
    }

    /////////////////////////

    /**
     * Chiamato dalla PeerHandler all'arrivo di ABActivityCommand
     *
     * @param childId
     * @return
     */
    public boolean addIncomingAllowedChild(String childId) {
        return addressBookManager.addIncomingAllowedChild(childId);
    }

    /**
     * Chiamato dall'AcceptorChild all'arrivo di un nuovo peer
     *
     * @param connectingChild
     */
    public void addConnectingChild(GroupChildHandler connectingChild) {
        //TODO sincronizzare forse
        this.connectingChild.put(connectingChild.getChildId(), connectingChild);
    }

    /**
     * Chiamato dal WelcomeCommand
     *
     * @param childId
     * @return
     */
    private boolean removeConnectingChild(String childId) {
        //TODO sincronizzare forse
        boolean removed = false;
        if (connectingChild.containsKey(childId)) {
            connectingChild.remove(childId);
            removed = true;
        }
        return removed;
    }

    ////////////////////////////////////////

    /**
     * Chiamato dal WelcomeCommand ed esegue solo se è Server
     *
     * @param child
     * @return
     */
    public boolean welcome(GroupChildHandler child) {
        boolean executed = false;
        if (isServer) {
            if (addressBookManager.addLowerPeer(child, false)) {
                boolean removed = removeConnectingChild(child.getChildId());
                if (removed) {
                    executed = child.sendAB(addressBookManager.getLowerAB());
                    if (addressBookManager.getLowerAB().size() > 1) {

                        signalNewBrother(child.getChildId(), child.getListeningPort());

                    }
                }
            } else {
                //too much children
                executed = newChildtoBestPeer(child.getChildId(), true);
            }
        }
        return executed;
    }

    private void signalNewBrother(String id, int port) {

        for (GroupChildHandler o : addressBookManager.getLowerAB().getAll(id)) {
            o.signalBrother(id, port);
        }
    }

    /**
     * Chiede ad uno dei suoi figli se può prenderlo
     * e schedula un comando negativo in caso di timeout
     *
     * @return
     */
    private boolean newChildtoBestPeer(String childId, boolean schedule) {
        GroupChildHandler bestChild = addressBookManager.getBestLowerPeer();
        log.info("search best child");
        return bestChild.sendNewChild(childId, schedule);
    }

    public boolean newChildtoBestPeer(String childId) {
        return newChildtoBestPeer(childId, false);
    }

    /**
     * Chiamato dal ConnectCommand
     *
     * @param child
     * @param oldChildId
     * @return
     */
    public boolean connect(GroupChildHandler child, String oldChildId) {
        boolean executed = false;

        if (addressBookManager.addLowerPeer(child, true)) {
            boolean removed = removeConnectingChild(oldChildId);
            if (isServer) {
                addressBookManager.addCentralPeer(selfInfo.getKey(), selfInfo.getValue());
                executed = child.sendAB(addressBookManager.getLowerAB(), addressBookManager.getCenterAB());
                addressBookManager.removeCentralPeer(selfInfo.getKey());
            } else {
                addressBookManager.addCentralPeer(selfInfo.getKey(), selfInfo.getValue());
                executed = child.sendAB(addressBookManager.getLowerAB(), addressBookManager.getCenterAB());
                addressBookManager.removeCentralPeer(selfInfo.getKey());
                application.getPeerHandler().signalSon(child.getChildId());
            }
            if (addressBookManager.getLowerAB().size() > 1) {
                signalNewBrother(child.getChildId(), child.getListeningPort());
            }
        } else {
            //child unknown - close connection
            log.error("on connect: child unknown!");
            child.shutdown();
        }

        return executed;
    }

    /**
     * Chiamato dal AckeNewChildCommand
     *
     * @param newChildId
     * @param groupLeaderHost
     * @param groupLeaderPort
     * @return
     */
    public boolean ackNewChild(String newChildId, String groupLeaderHost, int groupLeaderPort) {
        boolean success = false;
        if (groupLeaderHost != null) { //added
            GroupChildHandler newChild = connectingChild.get(newChildId);
            if (newChild != null) {
                success = newChild.sendConnectToGL(groupLeaderHost, groupLeaderPort);
                if (success) {
                    connectingChild.remove(newChildId);
                }
            } else {
                //girare il msg al padre
                getPeerHandler().sendAckNewChild(newChildId, groupLeaderHost, groupLeaderPort);
            }
        } else {
            log.error("AckNewChild groupLeaderHost null - Timeout");
        }
        return success;
    }

    /**
     * Chiamato dal AppraisalsCommand
     *
     * @param appraisals
     * @return
     */
    public boolean newAppraisals(Appraisals appraisals) {
        return addressBookManager.newAppraisals(appraisals);
    }

    /**
     * Chiamato dal ReconnectCommand
     *
     * @param child
     * @param exGroupLeaderId
     * @return
     */
    public boolean reconnect(GroupChildHandler child, String exGroupLeaderId, String peerId) {
        boolean sent = false;
        //TODO reconnect
        //se non c'e' l'exGroupLeaderId nel CAB mandare l'ab
        // il peer si può riconnettere perchè era il figlio di un fratello
        //altrimenti il child schedula un cmd sincrono aspettando la DeadBrother
        if (!addressBookManager.getCenterAB().peerKnown(exGroupLeaderId)) {
            log.info("reconnecting child: success - " + child);
            child.setChildId(peerId);
            sent = child.sendAB(addressBookManager.getLowerAB(), addressBookManager.getCenterAB());
        } else {
            log.info("aspettiamo un po non si sa mai arrivi una DeadBrother");
        }

        return sent;
    }

    public boolean signalNewUncle(String id, int port) {
        boolean signaled = true;
        Collection<GroupChildHandler> groupChildHandlers = addressBookManager.getLowerAB().getAll();

        for (GroupChildHandler groupChildHandler : groupChildHandlers) {
            signaled &= groupChildHandler.signalUncle(id, port);
            //todo if false then what?
        }

        return signaled;

    }

    public Collection<GroupChildHandler> getLabHandlers() {
        return addressBookManager.getLowerAB().getAll();
    }

    public Collection<GroupChildHandler> getLabHandlers(String excludePeer) {
        return addressBookManager.getLowerAB().getAll(excludePeer);
    }


    public boolean signalSonToGL(String childId) {
        return application.getPeerHandler().signalSon(childId);
    }

    public Set<String> getLABSet() {

        return addressBookManager.getLowerAB().getMap().keySet();
    }

    public Set<String> getCABSet() {
        return addressBookManager.getCenterAB().getMap().keySet();
    }

    public Set<String> getUABSet() {
        return addressBookManager.getUpperAB().getMap().keySet();
    }

    public Set<String> getAPLSet() {
        return addressBookManager.getAllowedPeerList();
    }

    public Set<String> getIAPSet() {
        return addressBookManager.getIncomingAllowedPeerList();
    }

    public boolean removeAllowedPeer(String peerID) {
        return addressBookManager.removeAllowedPeer(peerID);
    }

    public boolean removeIncomingPeer(String peerID) {
        return addressBookManager.getIncomingAllowedPeerList().remove(peerID);
    }

    public boolean removeBrother(String peerID) {
        return addressBookManager.removeCentralPeer(peerID);
    }

    public boolean removeUncle(String peerID) {
        return addressBookManager.removeUpperPeer(peerID);
    }

    public boolean signalDeadSon(String childId) {
        boolean signaled = true;
        addressBookManager.removeLowerPeer(childId);
        Collection<GroupChildHandler> all = addressBookManager.getLowerAB().getAll(childId);
        log.info("signalDeadSon to " + all);
        for (GroupChildHandler o : all) {
            signaled &= o.signalDeadSon(childId);
        }
        return signaled;
    }

    public boolean peerKnown(String childId) {
        return addressBookManager.peerKnown(childId);
    }

    public boolean isAllowed(String childId) {
        return addressBookManager.isAllowed(childId);
    }

    public boolean isServer() {
        return isServer;
    }

    public SimpleEntry<String, Integer> getSelfInfo() {
        return selfInfo;
    }

    public void setSelfInfo(String host, int port) {
        selfInfo = new SimpleEntry<String, Integer>(host, port);
    }


    public boolean horizontalBalance(int loadDifference, String designedGroupLeaderId) {
        //TODO
        SimpleEntry worstPeer = addressBookManager.getWorstLowerPeer();

        return getPeerHandler().sendHorizontalExchangeablePeer(worstPeer, designedGroupLeaderId);

    }

    public GroupChildHandler getChildHandler(String peerHost) {
        return addressBookManager.getChildHandler(peerHost);
    }

    public void executeHorizontalBalanceCommand(String exchangeablePeer, String designedGroupLeaderHost, int designedGroupLeaderListeningPort) {
        GroupChildHandler exchangeablePeerHandler = getChildHandler(exchangeablePeer);
        exchangeablePeerHandler.sendConnectToGL(designedGroupLeaderHost, designedGroupLeaderListeningPort);

    }
}
